<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
    <title>文件传输系统（自适应移动端）</title>
    <style>
      /* 基础样式重置 */
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        -webkit-tap-highlight-color: transparent; /* 消除移动端点击高亮 */
      }
      body {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
        background: #f5f5f5;
        min-height: 100vh;
        padding: 1rem;
      }

      /* 主容器 */
      .container {
        /*max-width: 600px;*/
        margin: 0 auto;
        background: white;
        border-radius: 12px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
        padding: 1.5rem;
      }

      /* 上传区域 */
      .upload-box {
        border: 2px dashed #e5e7eb;
        border-radius: 8px;
        padding: 1.5rem;
        text-align: center;
        margin-bottom: 2rem;
        transition: border-color 0.3s;
      }
      .upload-box:hover {
        border-color: #2563eb;
      }
      .upload-btn {
        background: #2563eb;
        color: white;
        padding: 0.75rem 1.5rem;
        border-radius: 6px;
        font-size: 1rem;
        border: none;
        cursor: pointer;
        margin-bottom: 1rem;
        transition: background 0.2s;
      }
      .upload-btn:hover {
        background: #1d4ed8;
      }
      #file-input {
        display: none;
      }
      .upload-tip {
        color: #6b7280;
        font-size: 0.875rem;
        margin-top: 15px;
      }
      #upload-status {
        color: #374151;
        margin-top: 1rem;
        font-size: 0.875rem;
        min-height: 1.2rem;
      }

      /* 文件分类区域 */
      .file-categories {
        margin-top: 1.5rem;
        display: flex;
        flex-direction: column;
        gap: 2rem;
      }

      .category-section {
        border-radius: 8px;
        overflow: hidden;
      }

      .category-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 0.75rem 1rem;
        font-weight: 600;
        color: white;
        margin-bottom: 1rem;
      }

      .category-header.images {
        background-color: #10b981; /* 绿色 - 图片类 */
      }

      .category-header.documents {
        background-color: #3b82f6; /* 蓝色 - 文档类 */
      }

      .category-header.others {
        background-color: #6b7280; /* 灰色 - 其他类 */
      }

      .category-icon {
        margin-right: 0.5rem;
      }

      .category-count {
        background-color: rgba(255, 255, 255, 0.2);
        padding: 0.2rem 0.5rem;
        border-radius: 12px;
        font-size: 0.75rem;
      }

      /* 文件列表 */
      .file-list {
        display: flex;
        flex-direction: column;
        gap: 0.75rem;
      }

      .file-item {
        background: #f8f9fa;
        padding: 1rem;
        border-radius: 8px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 1rem;
        transition: transform 0.1s, box-shadow 0.1s;
      }

      .file-item:hover {
        transform: translateY(-2px);
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
      }

      .file-info {
        display: flex;
        align-items: center;
        flex: 1;
        gap: 0.75rem;
        overflow: hidden;
      }

      .file-icon {
        font-size: 1.25rem;
      }

      .file-name {
        color: #374151;
        font-size: 0.875rem;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }

      .file-actions {
        display: flex;
        gap: 0.5rem;
        /* flex: .5; */
      }

      .action-btn {
        padding: 0.25rem 0.75rem;
        border-radius: 4px;
        font-size: 0.75rem;
        border: none;
        cursor: pointer;
        transition: opacity 0.2s, transform 0.1s;
      }

      .action-btn:active {
        transform: scale(0.95);
      }

      .delete-btn-files {
        background-color: #ef4444;
        color: #fff;
      }

      .download-btn {
        background: #2563eb;
        color: white;
      }

      .preview-btn,
      .preview-btn-file {
        background: #fcd34d;
        color: #111827;
      }

      .empty-state {
        color: #6b7280;
        text-align: center;
        padding: 2rem 1rem;
        background-color: #f8f9fa;
        border-radius: 8px;
        font-size: 0.875rem;
      }

      /* 图片预览模态框 */
      .preview-modal {
        display: none;
        position: fixed;
        z-index: 1000;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.9);
        padding: 1rem;
      }
      .modal-content {
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        position: relative;
      }
      .preview-img {
        max-width: 100%;
        max-height: 80vh;
        border-radius: 4px;
      }
      .nav-btn {
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        background: rgba(255, 255, 255, 0.1);
        color: white;
        border: none;
        padding: 1rem;
        font-size: 1.5rem;
        border-radius: 4px;
        cursor: pointer;
        transition: background 0.2s;
      }
      .nav-btn:hover {
        background: rgba(255, 255, 255, 0.2);
      }
      #prev-btn {
        left: 1rem;
      }
      #next-btn {
        right: 1rem;
      }
      .index-text {
        color: white;
        position: absolute;
        bottom: 1rem;
        font-size: 0.875rem;
      }
      .close-btn {
        position: absolute;
        top: 1rem;
        right: 1rem;
        background: none;
        border: none;
        color: white;
        font-size: 1.5rem;
        cursor: pointer;
      }

      /* 移动端适配 */
      @media (max-width: 600px) {
        .nav-btn {
          padding: 0.5rem;
          font-size: 1.25rem;
        }
        #prev-btn {
          left: 0.5rem;
        }
        #next-btn {
          right: 0.5rem;
        }

        .file-actions {
          gap: 0.25rem;
        }

        .action-btn {
          padding: 0.25rem 0.5rem;
          font-size: 0.7rem;
        }
      }

      /* 响应式布局调整 */
      @media (min-width: 768px) {
        .container {
          width: auto;
          max-width: auto;
        }
        .file-categories {
          flex-direction: row;
          flex-wrap: no-wrap;
          gap: 1.5rem;
        }

        .category-section {
          flex: 1 1 calc(33.333% - 1rem);
          min-width: 280px;
        }
      }
      #preview-video {
        width: 100%;
        height: auto;
        background: black;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1 style="color: #111827; margin-bottom: 1.5rem; font-size: 1.5rem">文件传输系统（自适应移动端）</h1>
      <!-- <p style="color: #6b7280; margin-bottom: 1.5rem;">当前服务器IP：{{ ip }}:5000</p> -->

      <!-- 上传区域 -->
      <div class="upload-box">
        <label for="file-input" class="upload-btn">选择文件上传（支持多选）</label>
        <input type="file" id="file-input" multiple />
        <p class="upload-tip">或拖拽文件到此处（支持图片/视频/文档等）</p>
        <p id="upload-status"></p>
      </div>

      <!-- 批量操作 -->
      <div style="display: flex; justify-content: flex-end; margin: 1rem 0">
        <button class="action-btn delete-btn-files" id="delete-btn-files">删除全部文件</button>
      </div>

      <!-- 文件分类列表 -->
      <div class="file-categories">
        <!-- 图片类文件 -->
        <div class="category-section">
          <div class="category-header images">
            <div>
              <span class="category-icon">📷</span>
              图片文件
            </div>
            <span class="category-count" id="images-count">0</span>
          </div>
          <div class="file-list" id="images-list">
            <!-- 图片文件会通过模板引擎填充 -->
            {% set image_count = 0 %} {% for file in files %} {% set ext = file.split('.')|last|lower %} {% if ext in ['mp4', 'mov', 'hevc', 'h265', 'jpg', 'jpeg', 'png', 'gif', 'bmp'] %} {% set
            image_count = image_count + 1 %}
            <div class="file-item">
              <div class="file-info">
                <span class="file-icon">🖼️</span>
                <span class="file-name">{{ file }}</span>
              </div>
              <div class="file-actions">
                <a href="/download/{{ file }}" class="action-btn download-btn">下载</a>
                <button class="action-btn preview-btn" data-filename="{{ file }}">预览</button>
                <button class="action-btn delete-btn-files" data-filename="{{ file }}">删除</button>
              </div>
            </div>
            {% endif %} {% endfor %} {% if image_count == 0 %}
            <div class="empty-state">暂无图片文件</div>
            {% endif %}
          </div>
        </div>

        <!-- 文档类文件 -->
        <div class="category-section">
          <div class="category-header documents">
            <div>
              <span class="category-icon">📄</span>
              文档文件
            </div>
            <span class="category-count" id="docs-count">0</span>
          </div>
          <div class="file-list" id="docs-list">
            <!-- 文档文件会通过模板引擎填充 -->
            {% set doc_count = 0 %} {% for file in files %} {% set ext = file.split('.')|last|lower %} {% if ext in ['txt', 'json', 'xlsx', 'docx', 'pdf', 'ppt', 'pptx', 'csv'] %} {% set doc_count =
            doc_count + 1 %}
            <div class="file-item">
              <div class="file-info">
                <span class="file-icon">📑</span>
                <span class="file-name">{{ file }}</span>
              </div>
              <div class="file-actions">
                <a href="/download/{{ file }}" class="action-btn download-btn">下载</a>
                {% if ext in ['txt', 'json', 'xlsx'] %}
                <button class="action-btn preview-btn-file" data-filename="{{ file }}">预览</button>
                {% endif %}
                <button class="action-btn delete-btn-files" data-filename="{{ file }}">删除</button>
              </div>
            </div>
            {% endif %} {% endfor %} {% if doc_count == 0 %}
            <div class="empty-state">暂无文档文件</div>
            {% endif %}
          </div>
        </div>

        <!-- 其他类文件 -->
        <div class="category-section">
          <div class="category-header others">
            <div>
              <span class="category-icon">📦</span>
              其他文件
            </div>
            <span class="category-count" id="others-count">0</span>
          </div>
          <div class="file-list" id="others-list">
            <!-- 其他文件会通过模板引擎填充 -->
            {% set other_count = 0 %} {% for file in files %} {% set ext = file.split('.')|last|lower %} {% if ext not in ['txt', 'json', 'xlsx', 'docx', 'pdf', 'ppt', 'pptx', 'csv','mp4', 'mov',
            'hevc', 'h265', 'jpg', 'jpeg', 'png', 'gif', 'bmp'] %} {% set other_count = other_count + 1 %}
            <div class="file-item">
              <div class="file-info">
                <span class="file-icon">📁</span>
                <span class="file-name">{{ file }}</span>
              </div>
              <div class="file-actions">
                <a href="/download/{{ file }}" class="action-btn download-btn">下载</a>
                <button class="action-btn delete-btn-files" data-filename="{{ file }}">删除</button>
              </div>
            </div>
            {% endif %} {% endfor %} {% if other_count == 0 %}
            <div class="empty-state">暂无其他文件</div>
            {% endif %}
          </div>
        </div>
      </div>
    </div>

    <!-- 图片预览模态框 -->
    <div class="preview-modal" id="preview-modal">
      <div class="modal-content">
        <button class="close-btn" id="close-btn">&times;</button>
        <img src="" class="preview-img" id="preview-img" />
        <!-- <video id="preview-video" controls style="max-width:100%; max-height:80vh; display:none;"></video> -->
        <video id="preview-video" style="max-width: 100%; max-height: 80vh" controls playsinline webkit-playsinline>
          <source src="{{ url_for('stream_file', filename=filename) }}" type="video/mp4" />
          你的浏览器不支持 video 标签
        </video>

        <button class="nav-btn" id="prev-btn">&lt;</button>
        <button class="nav-btn" id="next-btn">&gt;</button>
        <div class="index-text" id="index-text"></div>
      </div>
    </div>

    <script>
      // 全局变量
      let imageFiles = [];  // 所有可预览的图片文件列表
      let currentIndex = -1;  // 当前预览图片的索引

      // 页面加载后初始化
      document.addEventListener('DOMContentLoaded', () => {
          // 设置各类文件计数
          document.getElementById('images-count').textContent =
              document.querySelectorAll('#images-list .file-item').length;
          document.getElementById('docs-count').textContent =
              document.querySelectorAll('#docs-list .file-item').length;
          document.getElementById('others-count').textContent =
              document.querySelectorAll('#others-list .file-item').length;

          // 从后端获取文件列表并筛选图片
          const allFiles = {{ files|tojson }};
          imageFiles = allFiles.filter(file => {
              const ext = file.split('.').pop().toLowerCase();
              return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'mp4', 'mov', 'hevc', 'h265', 'avi', 'webm', 'mkv'].includes(ext);
          });

          // 引入 SparkMD5（用于快速计算文件 MD5）
          const sparkScript = document.createElement('script');
          sparkScript.src = 'https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.2/spark-md5.min.js';
          document.head.appendChild(sparkScript);

          // 上传文件处理（分块、断点续传、秒传）
          document.getElementById('file-input').addEventListener('change', handleFileUpload);

          // 预览按钮点击事件
          document.querySelectorAll('.preview-btn').forEach(btn => {
              btn.addEventListener('click', function() {
                  const filename = this.dataset.filename;
                  currentIndex = imageFiles.indexOf(filename);
                  showPreview();
              });
          });

          // 文件预览按钮点击事件
          document.querySelectorAll('.preview-btn-file').forEach(btn => {
              btn.addEventListener('click', function() {
                  const filename = this.dataset.filename;
                  window.open("/preview/" + filename)
              });
          });

          // 模态框关闭按钮
          document.getElementById('close-btn').addEventListener('click', () => {
              if(document.getElementById('preview-video').style.display == 'block'){
                  document.getElementById('preview-video').pause();
              }
              document.getElementById('preview-modal').style.display = 'none';
          });

          // 左右切换按钮
          document.getElementById('prev-btn').addEventListener('click', () => {
              if (currentIndex > 0) {
                  currentIndex--;
                  showPreview();
              }
          });
          document.getElementById('next-btn').addEventListener('click', () => {
              if (currentIndex < imageFiles.length - 1) {
                  currentIndex++;
                  showPreview();
              }
          });

          // 移动端手势滑动（左滑/右滑）
          let touchStartX = 0;
          document.getElementById('preview-img').addEventListener('touchstart', (e) => {
              touchStartX = e.touches[0].clientX;
          });
          document.getElementById('preview-img').addEventListener('touchend', (e) => {
              const touchEndX = e.changedTouches[0].clientX;
              const deltaX = touchEndX - touchStartX;
              if (deltaX > 50) {  // 右滑：上一张
                  document.getElementById('prev-btn').click();
              } else if (deltaX < -50) {  // 左滑：下一张
                  document.getElementById('next-btn').click();
              }
          });
      });

      // 处理文件上传（分块 5MB）
      async function handleFileUpload(e) {
          const files = e.target.files;
          if (files.length === 0) return;
          document.getElementById("file-input").disabled = true;
          document.getElementsByClassName("upload-btn")[0].style.backgroundColor = "gray";

          const statusEl = document.getElementById('upload-status');
          statusEl.textContent = `开始准备上传 ${files.length} 个文件...`;

          for (const file of files) {
              try {
                  statusEl.textContent = `计算哈希: ${file.name}`;
                  // 等待 SparkMD5 加载
                  if (typeof SparkMD5 === 'undefined') {
                      await new Promise(res => { sparkScript.onload = res; });
                  }

                  const fileMd5 = await computeFileHash(file);
                  statusEl.textContent = `文件 ${file.name} 的哈希: ${fileMd5}`;

                  // 初始化上传，获取已上传分块
                  const initForm = new FormData();
                  initForm.append('fileName', file.name);
                  initForm.append('fileMd5', fileMd5);
                  initForm.append('totalChunks', Math.ceil(file.size / (5 * 1024 * 1024)));
                  const initResp = await fetch('/upload/init', { method: 'POST', body: initForm });
                  const initJson = await initResp.json();
                  if (initJson.status === 'exists') {
                      statusEl.textContent = `秒传：${file.name} 已存在，跳过上传`;
                      continue;
                  }

                  statusEl.textContent = `开始分块上传: ${file.name}`;
                  await uploadFileInChunks(file, fileMd5, statusEl);

                  statusEl.textContent = `合并分块并校验: ${file.name}`;
                  const mergeForm = new FormData();
                  mergeForm.append('fileName', file.name);
                  mergeForm.append('fileMd5', fileMd5);
                  mergeForm.append('totalChunks', Math.ceil(file.size / (5 * 1024 * 1024)));
                  const mergeResp = await fetch('/upload/merge', { method: 'POST', body: mergeForm });
                  const mergeJson = await mergeResp.json();
                  if (mergeJson.status === 'ok') {
                      statusEl.textContent = `上传完成: ${file.name}`;
                  } else {
                      statusEl.textContent = `合并失败: ${mergeJson.msg || JSON.stringify(mergeJson)}`;
                  }
              } catch (err) {
                  statusEl.textContent = `上传错误 ${file.name}: ${err.message || err}`;
              }
          }

          setTimeout(() => {
              window.location.reload();
          }, 800);
      }


      // 计算文件 MD5（按 5MB 分片读取，避免一次性内存占用）
      function computeFileHash(file) {
          return new Promise((resolve, reject) => {
              const chunkSize = 5 * 1024 * 1024; // 5MB
              const chunks = Math.ceil(file.size / chunkSize);
              let current = 0;
              const spark = new SparkMD5.ArrayBuffer();
              const fileReader = new FileReader();

              fileReader.onload = e => {
                  spark.append(e.target.result);
                  current++;
                  if (current < chunks) {
                      loadNext();
                  } else {
                      const md5 = spark.end();
                      resolve(md5);
                  }
              };
              fileReader.onerror = err => reject(err);

              function loadNext() {
                  const start = current * chunkSize;
                  const end = Math.min(start + chunkSize, file.size);
                  fileReader.readAsArrayBuffer(file.slice(start, end));
              }

              loadNext();
          });
      }


      // 使用 XMLHttpRequest 上传单个分块以支持上传进度
      function uploadChunkXHR(fileMd5, index, blob, onProgress) {
          return new Promise((resolve, reject) => {
              const xhr = new XMLHttpRequest();
              const fd = new FormData();
              fd.append('fileMd5', fileMd5);
              fd.append('index', index);
              fd.append('chunk', blob);
              xhr.open('POST', '/upload/chunk');
              xhr.upload.onprogress = function (e) {
                  if (e.lengthComputable && onProgress) {
                      onProgress(e.loaded / e.total, index);
                  }
              };
              xhr.onload = function () {
                  if (xhr.status >= 200 && xhr.status < 300) {
                      try {
                          const json = JSON.parse(xhr.responseText);
                          resolve(json);
                      } catch (e) {
                          resolve({ status: 'ok' });
                      }
                  } else {
                      reject(new Error('Chunk upload failed: ' + xhr.status));
                  }
              };
              xhr.onerror = function () { reject(new Error('Chunk upload error')); };
              xhr.send(fd);
          });
      }


      // 并发上传分块（限并发数）
      async function uploadFileInChunks(file, fileMd5, statusEl) {
          const chunkSize = 5 * 1024 * 1024;
          const total = Math.ceil(file.size / chunkSize);

          // 不再检查已上传分块，始终从 0 开始上传（客户端按顺序上传所有分块）
          const uploadedSet = new Set();

          const concurrency = 3;
          let inFlight = 0;
          let idx = 0;
          let uploadedCount = uploadedSet.size;

          return new Promise((resolve, reject) => {
              function next() {
                  if (idx >= total && inFlight === 0) {
                      resolve();
                      return;
                  }
                  while (inFlight < concurrency && idx < total) {
                      const currentIndex = idx++;
                      if (uploadedSet.has(currentIndex)) {
                          // 跳过已上传
                          uploadedCount++;
                          statusEl.textContent = `上传进度 ${file.name}: ${uploadedCount}/${total}`;
                          continue;
                      }
                      inFlight++;
                      const start = currentIndex * chunkSize;
                      const end = Math.min(start + chunkSize, file.size);
                      const blob = file.slice(start, end);
                      uploadChunkXHR(fileMd5, currentIndex, blob, (pct, index) => {
                          // 可扩展：显示每个分块进度
                          statusEl.textContent = `上传中 ${file.name} 分块 ${index} ${Math.round(pct*100)}%`;
                      }).then(() => {
                          inFlight--;
                          uploadedCount++;
                          statusEl.textContent = `上传进度 ${file.name}: ${uploadedCount}/${total}`;
                          next();
                      }).catch(err => {
                          reject(err);
                      });
                  }
              }
              next();
          });
      }

      // 显示图片预览
      function showPreview() {
          if (currentIndex === -1) return;
          const modal = document.getElementById('preview-modal');
          const imgEl = document.getElementById('preview-img');
          const indexEl = document.getElementById('index-text');
          const videoEl = document.getElementById('preview-video');


          const filename = imageFiles[currentIndex];
          const ext = filename.split('.').pop().toLowerCase();

          if (['mp4', 'mov', 'hevc', 'h265', 'avi', 'webm', 'mkv'].includes(ext)) {
              // 视频
              imgEl.style.display = 'none';
              videoEl.style.display = 'block';
              videoEl.src = `/stream/${filename}`;

              videoEl.load();
              // videoEl.play();

              // 切换按钮
              document.getElementById('prev-btn').style.display = 'none';
              document.getElementById('next-btn').style.display = 'none';
          } else {
              // 图片
              videoEl.pause();
              videoEl.style.display = 'none';
              videoEl.src = '';
              imgEl.style.display = 'block';
              imgEl.src = `/download/${filename}`;

              // 切换按钮
              document.getElementById('prev-btn').style.display = 'block';
              document.getElementById('next-btn').style.display = 'block';
          }

          indexEl.textContent = `${currentIndex + 1}/${imageFiles.length}`;
          modal.style.display = 'block';
      }

      // 点击模态框背景关闭
      document.getElementById('preview-modal').addEventListener('click', (e) => {
          if (e.target === e.currentTarget) {
              e.currentTarget.style.display = 'none';
          }
      });

      // 删除全部文件按钮
      document.getElementById('delete-btn-files').addEventListener('click', () => {
          let isConfirmed = window.confirm("你确定要删除所有文件吗？此操作不可恢复！");
          if(!isConfirmed) return;
          const statusEl = document.getElementById('upload-status');
          fetch('/deletefiles', {
              method: 'GET'
          })
              .then(res => res.json())
              .then(data => {
                  if (data.status === 'success') {
                      statusEl.textContent = data.msg;
                      setTimeout(() => {
                          window.location.reload();  // 刷新
                      }, 1000);
                  }
              })
      });

      // 单个文件删除事件
      document.querySelectorAll('.delete-btn-files[data-filename]').forEach(btn => {
          btn.addEventListener('click', function(e) {
              const filename = this.dataset.filename;
              let isConfirmed = window.confirm(`你确定要删除文件"${filename}"吗？`);
              if(!isConfirmed) return;

              const statusEl = document.getElementById('upload-status');
              fetch(`/delete/${filename}`, {
                  method: 'GET'
              })
              .then(res => res.json())
              .then(data => {
                  if (data.status === 'success') {
                      statusEl.textContent = `已删除：${filename}`;
                      setTimeout(() => {
                          window.location.reload();  // 刷新
                      }, 500);
                  } else {
                      statusEl.textContent = `删除失败：${data.msg}`;
                  }
              })
          });
      });
    </script>
  </body>
</html>
